program TerrainRendering;

{$APPTYPE CONSOLE}

{$R *.res}

uses
  System.SysUtils,
  SampleCommon,
  DIrr in '..\..\DSource\DIrr.pas',
  DIrr.Irr in '..\..\DSource\DIrr.Irr.pas',
  DIrr.GUI in '..\..\DSource\DIrr.GUI.pas',
  DIrr.Color in '..\..\DSource\DIrr.Color.pas',
  DIrr.Rect in '..\..\DSource\DIrr.Rect.pas',
  DIrr.Event in '..\..\DSource\DIrr.Event.pas',
  DIrr.Vector2D in '..\..\DSource\DIrr.Vector2D.pas',
  DIrr.Vector3D in '..\..\DSource\DIrr.Vector3D.pas',
  DIrr.Video in '..\..\DSource\DIrr.Video.pas',
  DIrr.Base in '..\..\DSource\DIrr.Base.pas',
  DIrr.Types in '..\..\DSource\DIrr.Types.pas',
  DIrr.Scene in '..\..\DSource\DIrr.Scene.pas',
  DIrr.Structs in '..\..\DSource\DIrr.Structs.pas',
  DIrr.Aabbox3D in '..\..\DSource\DIrr.Aabbox3D.pas',
  DIrr.Matrix4 in '..\..\DSource\DIrr.Matrix4.pas',
  DIrr.Texture in '..\..\DSource\DIrr.Texture.pas',
  DIrr.Collections in '..\..\DSource\DIrr.Collections.pas',
  DIrr.Dimension2d in '..\..\DSource\DIrr.Dimension2d.pas',
  DIrr.Buffer in '..\..\DSource\DIrr.Buffer.pas',
  DIrr.Consts in '..\..\DSource\DIrr.Consts.pas';

type
  TMyEventReceiver = class(TEventReceiver)
  private
    FTerrain: TSceneNode;
    FSkyBox: TSceneNode;
    FSkyDome: TSceneNode;
    FShowBox: Boolean;
    FShowDebug: Boolean;
  public
    procedure Init(Terrain, SkyBox, SkyDome: TSceneNode);
    function OnEvent(const Event: SEvent): Boolean; override;
  end;

{ TMyEventReceiver }

procedure TMyEventReceiver.Init(Terrain, SkyBox, SkyDome: TSceneNode);
begin
  FTerrain := Terrain;
  FSkyBox := SkyBox;
  FSkyDome := SkyDome;

  FShowBox := True;
  FSkyBox.SetVisible(FShowBox);
  FSkyDome.SetVisible(not FShowBox);
end;

function TMyEventReceiver.OnEvent(const Event: SEvent): Boolean;
var
  MaterialPtr: PMaterial;
begin
  Result := False;

  // check if user presses the key 'W' or 'D'
  if (Event.EventType = EET_KEY_INPUT_EVENT) and (not Event.KeyInput.PressedDown) then
  begin
    case Event.KeyInput.Key of
      KEY_KEY_W:  // switch wire frame mode
      begin
        MaterialPtr := FTerrain.GetMaterial(0);
        FTerrain.SetMaterialFlag(EMF_WIREFRAME, not MaterialPtr^.Wireframe);
        FTerrain.SetMaterialFlag(EMF_POINTCLOUD, False);
        Result := True;
      end;

      KEY_KEY_P:  // switch wire frame mode
      begin
        MaterialPtr := FTerrain.GetMaterial(0);
        FTerrain.SetMaterialFlag(EMF_POINTCLOUD, not MaterialPtr^.PointCloud);
        FTerrain.SetMaterialFlag(EMF_WIREFRAME, False);
        Result := True;
      end;

      KEY_KEY_D: // toggle detail map
      begin
        MaterialPtr := FTerrain.GetMaterial(0);
        if MaterialPtr^.MaterialType = EMT_SOLID then
          FTerrain.SetMaterialType(EMT_DETAIL_MAP)
        else
          FTerrain.SetMaterialType(EMT_SOLID);
        Result := True;
      end;

      KEY_KEY_S: // toggle skies
      begin
        FShowBox := not FShowBox;
        FSkyBox.SetVisible(FShowBox);
        FSkyDome.SetVisible(not FShowBox);
        Result := True;
      end;

      KEY_KEY_X: // toggle debug information
      begin
        FShowDebug := not FShowDebug;
        if FShowDebug then
          FTerrain.SetDebugDataVisible(EDS_BBOX_ALL)
        else
          FTerrain.SetDebugDataVisible(EDS_OFF);
        Result := True;
      end;
    end;
  end;
end;

var
  DriverType: E_DRIVER_TYPE;
  Device: TIrrlichtDevice;
  Driver: TVideoDriver;
  Env: TGUIEnvironment;
  Smgr: TSceneManager;
  Params: SIrrlichtCreationParameters;
  Camera: TCameraSceneNode;
  Terrain: TTerrainSceneNode;
  Selector: TTriangleSelector;
  Anim: TSceneNodeAnimator;
  Buffer: TDynamicMeshBuffer;
  DataPtr: P3DVertex2TCoords;
  SkyBox, SkyDome: TSceneNode;
  Receiver: TMyEventReceiver;
  LastFPS, FPS: Integer;
  CamPos: TVector3DF;
  Str: String;

begin
  try
    { TODO -oUser -cConsole Main : Insert code here }
    DriverType := DriverChoiceConsole;
    if DriverType = EDT_COUNT then
    begin
      ExitCode := 1;
      Exit;
    end;

    // create device with full flexibility over creation parameters
    // you can add more parameters if desired, check irr::SIrrlichtCreationParameters
    Params.Init;
    Params.DriverType := DriverType;
    Params.WindowSize := Dimension2DU(640, 480);
    Device := CreateDevice(Params);
    if Device = nil then
    begin
      ExitCode := 1;   // could not create selected driver.
      Exit;
    end;

    (*
    First, we add standard stuff to the scene: A nice irrlicht engine
    logo, a small help text, a user controlled camera, and we disable
    the mouse cursor.
    *)
    Driver := Device.GetVideoDriver;
    Smgr := Device.GetSceneManager;
    Env := Device.GetGUIEnvironment;

    Driver.SetTextureCreationFlag(ETCF_ALWAYS_32_BIT, True);

    // add irrlicht logo
    Env.AddImage(Driver.GetTexture('..\..\media\irrlichtlogo2.png'), Vector2D(10, 10));

    // set other font
    Env.GetSkin.SetFont(Env.GetFont('..\..\media\fontlucida.png'));

    // add some help text
    Env.AddStaticText
    (
      'Press ''W'' to change wireframe mode'#13#10 +
        'Press ''D'' to toggle detail map'#13#10 +
        'Press ''S'' to toggle skybox/skydome',
      IrrRect(10, 421, 250, 475),
      True, True, nil, -1, True
    );

    // add camera
    Camera := Smgr.AddCameraSceneNodeFPS(nil, 100, 1.2);

    Camera.SetPosition(Vector3DF(2700 * 2, 255 * 2, 2600 * 2));
    Camera.SetTarget(Vector3DF(2397 * 2, 343 * 2, 2700 * 2));
    Camera.SetFarValue(42000);

    // disable mouse cursor
    //Device.GetCursorControl.SetVisible(False);

    (*
    Here comes the terrain renderer scene node: We add it just like any
    other scene node to the scene using
    ISceneManager::addTerrainSceneNode(). The only parameter we use is a
    file name to the heightmap we use. A heightmap is simply a gray scale
    texture. The terrain renderer loads it and creates the 3D terrain from
    it.

    To make the terrain look more big, we change the scale factor of
    it to (40, 4.4, 40). Because we don't have any dynamic lights in the
    scene, we switch off the lighting, and we set the file
    terrain-texture.jpg as texture for the terrain and detailmap3.jpg as
    second texture, called detail map. At last, we set the scale values for
    the texture: The first texture will be repeated only one time over the
    whole terrain, and the second one (detail map) 20 times.
    *)

    // add terrain scene node
    Terrain := Smgr.AddTerrainSceneNode
               (
                 '..\..\media\terrain-heightmap.bmp',
                 nil,                           // parent node
                 -1,                            // node id
                 Vector3DF(0, 0, 0),            // position
                 Vector3DF(0, 0, 0),            // rotation
                 Vector3DF(40, 4.4, 40),        // scale
                 IrrColor(255, 255, 255, 55),  // vertexColor
                 5,                             // maxLOD
                 ETPS_17,                       // patchSize
                 4                              // smoothFactor
               );

    Terrain.SetMaterialFlag(EMF_LIGHTING, False);

    Terrain.SetMaterialTexture(0, Driver.GetTexture('..\..\media\terrain-texture.jpg'));
    Terrain.SetMaterialTexture(1, Driver.GetTexture('..\..\media\detailmap3.jpg'));

    Terrain.SetMaterialType(EMT_DETAIL_MAP);

    Terrain.ScaleTexture(1.0, 20.0);

    (*
    To be able to do collision with the terrain, we create a triangle selector.
    If you want to know what triangle selectors do, just take a look into the
    collision tutorial. The terrain triangle selector works together with the
    terrain. To demonstrate this, we create a collision response animator
    and attach it to the camera, so that the camera will not be able to fly
    through the terrain.
    *)

    // create triangle selector for the terrain
    Selector := Smgr.CreateTerrainTriangleSelector(Terrain, 0);
    Terrain.SetTriangleSelector(Selector);

    // create collision response animator and attach it to the camera
    Anim := Smgr.CreateCollisionResponseAnimator
            (
              Selector, Camera, Vector3DF(60, 100, 60),
              Vector3DF(0, 0, 0),
              Vector3DF(0, 50, 0)
            );
    Selector.Drop;
    Camera.AddAnimator(Anim);
    Anim.Drop;

	  (* If you need access to the terrain data you can also do this directly via the following code fragment.*)
    Buffer := Smgr.CreateDynamicMeshBuffer(EVT_2TCOORDS, EIT_16BIT);
    Terrain.GetMeshBufferForLOD(Buffer, 0);
    DataPtr := P3DVertex2TCoords(Buffer.GetVertexBuffer.GetData);
    // Work on data or get the IndexBuffer with a similar call.
    Buffer.Drop;  // When done drop the buffer again.

    (*
    To make the user be able to switch between normal and wireframe mode,
    we create an instance of the event receiver from above and let Irrlicht
    know about it. In addition, we add the skybox which we already used in
    lots of Irrlicht examples and a skydome, which is shown mutually
    exclusive with the skybox by pressing 'S'.
    *)

    // create skybox and skydome
    Driver.SetTextureCreationFlag(ETCF_CREATE_MIP_MAPS, False);

    SkyBox := Smgr.AddSkyBoxSceneNode
              (
                Driver.GetTexture('..\..\media\irrlicht2_up.jpg'),
                Driver.GetTexture('..\..\media\irrlicht2_dn.jpg'),
                Driver.GetTexture('..\..\media\irrlicht2_lf.jpg'),
                Driver.GetTexture('..\..\media\irrlicht2_rt.jpg'),
                Driver.GetTexture('..\..\media\irrlicht2_ft.jpg'),
                Driver.GetTexture('..\..\media\irrlicht2_bk.jpg')
              );
    SkyDome := Smgr.AddSkyDomeSceneNode
               (
                 Driver.GetTexture('..\..\media\skydome.jpg'),
                 16, 8, 0.95, 2.0
               );

    Driver.SetTextureCreationFlag(ETCF_CREATE_MIP_MAPS, True);

    // create event receiver
    Receiver := TMyEventReceiver.Create;
    Receiver.Init(Terrain, SkyBox, SkyDome);
    Device.SetEventReceiver(Receiver);

    (*
    That's it, draw everything.
    *)

    LastFPS := -1;
    while (Device.Run and Assigned(Driver)) do
    begin
      if Device.IsWindowActive then
      begin
        Driver.BeginScene(True, True, IrrColor(0, 200, 200, 200));

        Smgr.DrawAll;
        Env.DrawAll;

        Driver.EndScene;

        // display frames per second in window title
        FPS := Driver.GetFPS;
        if LastFPS <> Driver.GetFPS then
        begin
          Str := 'Terrain Renderer - Irrlicht Engine [' + String(Driver.GetName);
          Str := Str + '] FPS:' + IntToStr(FPS);
          // Also print terrain height of current camera position
          // We can use camera position because terrain is located at coordinate origin
          Str := Str + ' Height: ';
          Camera.GetAbsolutePosition(CamPos);
          Str := Format('%0.2f', [Terrain.GetHeight(CamPos.X, CamPos.Z)]);

          Device.SetWindowCaption(PChar(Str));
          LastFPS := FPS;
        end;

      end;
    end;

    Device.Drop;
  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;
end.
